﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ST7565LcdPlugin.cs" company="Voogd en Voogd Diensten">
//   Copyright 2008-2009 Voogd en Voogd Diensten
// </copyright>
// <author>Jelle</author>
// <summary>
//   De ST7565LcdPlugin class.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace GLCDST7565Plugin
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;

    using GLCDBaseTypes.BaseTypes;
    using GLCDBaseTypes.PluginInterfaces;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class ST7565LcdPlugin : ILcdPlugin   
    {
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref int pvParam, uint fWinIni);

        /* Constants used for User32 calls. */
        const uint SPI_GETFONTSMOOTHING = 74;
        const uint SPI_SETFONTSMOOTHING = 75;
        const uint SPI_UPDATEINI = 0x1;

        public string GetPluginDescription()
        {
            return "ST7565 Plugin for GLCD Designer";
        }

        /// <summary>
        /// Get arduino code
        /// </summary>
        /// <param name="hexList">De Hex list param van het type List</param>
        /// <param name="codeElementName">De Code element name param van het type string</param>
        /// <param name="width">De Width param van het type int</param>
        /// <returns>Retourneert String</returns>
        public string GetArduinoCode(List<HexRepresentationItem> hexList, string codeElementName, int width)
        {
            StringBuilder sb = new StringBuilder();

            // Format the string
            sb.AppendLine("static unsigned char __attribute__ ((progmem)) " + codeElementName + "[]={");

            int lineLength = 0;
            foreach (HexRepresentationItem hexRepresentationItem in hexList)
            {
                if (lineLength == width)
                {
                    sb.Append("\n");
                    lineLength = 0;
                }

                sb.Append(hexRepresentationItem.HexValue + ", ");

                lineLength++;
            }

            sb.AppendLine("};");

            return sb.ToString();
        }

        /// <summary>
        /// Get arduino code
        /// </summary>
        /// <param name="bitmap">De Bitmap param van het type Bitmap</param>
        /// <param name="codeElementName">De Code element name param van het type string</param>
        /// <returns>Retourneert String</returns>
        public string GetArduinoCode(Bitmap bitmap, string codeElementName)
        {
            List<HexRepresentationItem> hexItems = this.GetHexList(bitmap);

            return this.GetArduinoCode(hexItems, codeElementName, Math.Min(16, bitmap.Width));
        }

        /// <summary>
        /// Get hex list
        /// </summary>
        /// <param name="bitmap">De Bitmap param van het type Bitmap</param>
        /// <returns>Retourneert List</returns>
        public List<HexRepresentationItem> GetHexList(Bitmap bitmap)
        {
            List<HexRepresentationItem> hexItems = new List<HexRepresentationItem>();

            Color white = Color.FromArgb(255, 255, 255);

            int treshold = 100;

            // Create a hex representation from this bitmap
            string binaryLine = string.Empty;
            HexRepresentationItem hexItem = null;

            int yoffset = 0;

            while (yoffset < bitmap.Height)
            {
                for (int x = 0; x < bitmap.Width; x++)
                {
                    for (int y = yoffset; (y < bitmap.Height) && ((y - yoffset) < 8); y++)
                    {
                        if (!IsAboveTreshold(bitmap.GetPixel(x, y), treshold))
                        {
                            binaryLine += '1';
                        }
                        else
                        {
                            binaryLine += '0';
                        }

                        if (binaryLine.Length == 8)
                        {
                            // Use this line to generate a hex presentation
                            hexItem = new HexRepresentationItem();
                            hexItem.FromBinary(binaryLine);

                            hexItems.Add(hexItem);

                            binaryLine = string.Empty;
                        }
                    }

                    if (binaryLine.Length > 0)
                    {
                        // Use this line to generate a hex presentation
                        hexItem = new HexRepresentationItem();
                        hexItem.FromBinary(binaryLine);

                        hexItems.Add(hexItem);
                    }
                }

                yoffset += 8;
            }

            return hexItems;
        }

        /// <summary>
        /// Create bitmap from hex
        /// </summary>
        /// <param name="hexList">De Hex list param van het type List</param>
        /// <param name="width">De Width param het type int</param>
        /// <param name="zoomFactor">De Zoom factor param het type int</param>
        /// <returns>Retourneert Bitmap</returns>
        public Bitmap CreateBitmapFromHex(List<HexRepresentationItem> hexList, int width, int zoomFactor)
        {
            //Bitmap bitmap = new Bitmap(width * zoomFactor, (hexList.Count / (width / zoomFactor)));
            Bitmap bitmap = new Bitmap(width * zoomFactor, 5000);
            Graphics graphic = Graphics.FromImage(bitmap);

            // Fill it white
            graphic.FillRectangle(new SolidBrush(Color.White), 0, 0, bitmap.Width, bitmap.Height);

            Brush blackPen = new SolidBrush(Color.Black);

            // De karakters zijn gekanteld!
            // dus niet x,y maar y,x
            // De regels lopen dus naar beneden, zo moet het grid ook geintepreteerd worden

            // ST7565 LCD is 8 regels van 16 en dan opnieuw (memory blocks)
            // Dus bij 8 * 16 'wrap' naar start            
            int xoffset = 0;
            int teller = 0;

            int y = 0;
            foreach (HexRepresentationItem hexItem in hexList)
            {
                if (teller == width)
                {
                    xoffset = xoffset + 8;
                    y = 0;
                    teller = 0;
                }

                string binaryText = hexItem.ToBinary();

                for (int x = 0; x < binaryText.Length; x++)
                {
                    if (binaryText[x] == '1')
                    {
                        graphic.FillRectangle(blackPen, (y * zoomFactor), ((xoffset + x) * zoomFactor), zoomFactor, zoomFactor);
                    }
                }

                y++;
                teller++;
            }

            return bitmap;
        }

        /// <summary>
        /// Get arduino code for font
        /// </summary>
        /// <param name="font">De Font param van het type Font</param>
        /// <param name="desiredWidth">De Desired width param van het type int</param>
        /// <param name="desiredHeight">De Desired height param van het type int</param>
        /// <param name="codeElementName">De Code element name param van het type string</param>
        /// <returns>Retourneert String</returns>
        public string GetArduinoCodeForFont(Font font, int desiredWidth, int desiredHeight, string codeElementName)
        {
            List<HexRepresentationItem> hexItems = this.GetHexList(font, desiredWidth, desiredHeight);

            return this.GetArduinoCode(hexItems, codeElementName, Math.Min(16, desiredWidth));
        }

        /// <summary>
        /// Get hex list
        /// </summary>
        /// <param name="font">De Font param van het type Font</param>
        /// <param name="desiredWidth">De Desired width param van het type int</param>
        /// <param name="desiredHeight">De Desired height param van het type int</param>
        /// <returns>Retourneert List</returns>
        public List<HexRepresentationItem> GetHexList(Font font, int desiredWidth, int desiredHeight)
        {
            bool smoothingreset = false;

            // Disable font smoothing if available
            if (true)
            {
                this.DisableFontSmoothing();

                smoothingreset = true;
            }

            // Create a bitmap for each letter, capture it and add it to the hex array
            List<HexRepresentationItem> hexList = new List<HexRepresentationItem>();

            Bitmap bmp = new Bitmap(desiredWidth, desiredHeight);
            Graphics g = Graphics.FromImage(bmp);

            for (int i = 0; i < 255; i++)
            {
                g.FillRectangle(Brushes.White, 0, 0, desiredWidth, desiredHeight);

                SizeF extent = g.MeasureString(((char)i).ToString(), font);

                g.DrawString(((char)i).ToString(), font, Brushes.Black, new PointF((desiredWidth - extent.Width) / 2, (desiredHeight - extent.Height) / 2));

                //using (StringFormat s = new StringFormat())
                //{

                //    s.Alignment = StringAlignment.Center;
                //    s.LineAlignment = StringAlignment.Center;

                //    SizeF size;
                //    using (
                //        Font f2 = AppropriateFont(
                //            g, 5, 50, new Size(desiredWidth, desiredHeight), ((char)i).ToString(), font, out size))
                //    {
                //        PointF p = new PointF((desiredWidth - size.Width) / 2, (desiredHeight - size.Height) / 2);
                //        g.DrawString(((char)i).ToString(), f2, Brushes.Black, p);
                //        //bmp.Save(@"C:\fonts\font_" + i.ToString() + ".bmp");
                //    }
                //}

                Bitmap toScan = this.MakeGrayscale(bmp);

                hexList.AddRange(this.GetHexList(toScan));
            }

            if (smoothingreset)
            {
                this.EnableFontSmoothing();
            }

            return hexList;
        }

        /// <summary>
        /// Appropriate font
        /// </summary>
        /// <param name="g">De G param van het type Graphics</param>
        /// <param name="minFontSize">De Minimum font size param van het type float</param>
        /// <param name="maxFontSize">De Maximum font size param van het type float</param>
        /// <param name="layoutSize">De Layout size param van het type Size</param>
        /// <param name="s">De S param van het type string</param>
        /// <param name="f">De F param van het type Font</param>
        /// <param name="extent">De Extent param van het type SizeF</param>
        /// <returns>Retourneert Font</returns>
        public static Font AppropriateFont(Graphics g, float minFontSize, float maxFontSize, Size layoutSize, string s, Font f, out SizeF extent)
        {
            if (maxFontSize == minFontSize) f = new Font(f.FontFamily, minFontSize, f.Style);

            extent = g.MeasureString(s, f);

            if (maxFontSize <= minFontSize) return f;

            float hRatio = layoutSize.Height / extent.Height;
            float wRatio = layoutSize.Width / extent.Width;
            float ratio = (hRatio < wRatio) ? hRatio : wRatio;

            float newSize = f.Size * ratio;

            if (newSize < minFontSize) newSize = minFontSize;
            else if (newSize > maxFontSize) newSize = maxFontSize;

            f = new Font(f.FontFamily, newSize, f.Style);
            extent = g.MeasureString(s, f);

            return f;
        }

        /// <summary>
        /// To string
        /// </summary>
        /// <returns>Retourneert String</returns>
        public override string ToString()
        {
            return this.GetPluginDescription();
        }

        /// <summary>
        /// Is above treshold
        /// </summary>
        /// <param name="colorToEvaluate">De Color to evaluate param van het type Color</param>
        /// <param name="treshold">De Treshold param van het type int</param>
        /// <returns>Retourneert Bool</returns>
        private bool IsAboveTreshold(Color colorToEvaluate, int treshold)
        {
            return (colorToEvaluate.R > treshold) && (colorToEvaluate.G > treshold) && (colorToEvaluate.B > treshold);
        }

        private Bitmap MakeGrayscale(Bitmap original)
        {
            //make an empty bitmap the same size as original
            Bitmap newBitmap = new Bitmap(original.Width, original.Height);

            for (int i = 0; i < original.Width; i++)
            {
                for (int j = 0; j < original.Height; j++)
                {
                    //get the pixel from the original image
                    Color originalColor = original.GetPixel(i, j);

                    //create the grayscale version of the pixel
                    int grayScale = (int)((originalColor.R * .3) + (originalColor.G * .59)
                        + (originalColor.B * .11));

                    //create the color object
                    Color newColor = Color.FromArgb(grayScale, grayScale, grayScale);

                    //set the new image's pixel to the grayscale version
                    newBitmap.SetPixel(i, j, newColor);
                }
            }

            return newBitmap;
        }

        private Boolean GetFontSmoothing()
        {
            bool iResult;
            int pv = 0;
            /* Call to systemparametersinfo to get the font smoothing value. */
            iResult = SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, ref pv, 0);
            if (pv > 0)
            {
                //pv > 0 means font smoothing is on.
                return true;
            }
            else
            {
                //pv == 0 means font smoothing is off.
                return false;
            }
        }

        private void DisableFontSmoothing()
        {
            bool iResult;
            int pv = 0;
            /* Call to systemparametersinfo to set the font smoothing value. */
            iResult = SystemParametersInfo(SPI_SETFONTSMOOTHING, 0, ref pv, SPI_UPDATEINI);
        }

        private void EnableFontSmoothing()
        {
            bool iResult;
            int pv = 0;
            /* Call to systemparametersinfo to set the font smoothing value. */
            iResult = SystemParametersInfo(SPI_SETFONTSMOOTHING, 1, ref pv, SPI_UPDATEINI);
        }
    }
}
